""" -*- mode: python; coding: utf-8; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
"""

import __main__, sys, traceback, os, types, pickle

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

CYAN="\033[36m"
GREEN="\033[32m"
WHITE="\033[37m"
MAGENTA="\033[35m"
RESET="\033[0m"

intromit_ctx="__main__"

def get_ctx():
    return eval(__main__.__intromit__.intromit_ctx)

def get_ctx_str():
    return __main__.__intromit__.intromit_ctx

def get_ctx_str_wo_main_dot():
    str=__main__.__intromit__.intromit_ctx[9:]
    if str: str+="."
    return str

def set_ctx(ctx_str):
    __main__.__intromit__.intromit_ctx=ctx_str

def extract_object(string):
    obj=None
    try: obj=eval(string, get_ctx().__dict__)
    except Exception, inst:

        tb=sys.exc_info()[2] # third return value is a traceback object
        frames=traceback.extract_tb(tb) # list of frames
        filename=frames[-1][0]

        if (filename=='<string>' and frames[-1][2]=='<module>') or os.path.basename(filename)==__name__+'.py':
            # nothing failed inside provided functions
            return None
        else:
            traceback.print_exc(file=sys.stdout)
            return "._Exception"

    # never return None
    if obj!=None: return obj
    else: return "._None"

def completion(string):
    dot=string.rfind(".")
    if dot<0: prefix=""
    else: prefix=string[:dot]
    sufix=string[dot+1:]
    #print "completion ["+string+"]  ["+prefix+"]  ["+sufix+"]"

    base=None
    if dot<0: base=get_ctx()
    else: base=extract_object(prefix)
    if not base: return []

    res=[]
    for k in dir(base):
        if k==sufix:
            # complete word matched, append '.' or '('
            match=prefix+(prefix and "." or "")+k
            if callable(extract_object(prefix+"."+k)):
                match+='('
            else:
                match+='.'
            res.append(match)
        elif len(k)>=len(sufix) and k[:len(sufix)]==sufix and (sufix!="" or k[0]!="_"):
            res.append(prefix+(prefix and "." or "")+k)
    return res;

def load_ex(elf_file):
    module=__main__.__intromit__.load(elf_file)
    setattr(get_ctx(), module.__name__, module)

def ls_ex(obj_str):
    if obj_str:
        ctx=get_ctx_str()
        while obj_str.startswith(".."):
            if ctx.find(".")<0:
                print "trying to go below <root>"
                return
            ctx=ctx[:ctx.rfind(".")]
            obj_str=obj_str[2:]
            if len(obj_str) and obj_str[0] is "/": obj_str=obj_str[1:]

        try:
            if obj_str:
                obj=eval(ctx+"."+obj_str)
            else:
                obj=eval(ctx)
        except:
            print ctx+"."+obj_str+" is not a valid context"
            return
    else:
        obj=get_ctx()
    attr=[a for a in dir(obj) if not a.startswith("_")]

    # calculate maximum width
    width=0
    for a in attr:
        if len(a) > width:
            width=len(a)
    format="%"+str(width)+"s"+RESET+" :"

    for a in attr:
        sub=getattr(obj, a)
        sub_type=""
        if callable(sub):
            sys.stdout.write(WHITE)
            sub_type="method"
        elif type(sub) is types.ModuleType:
            sys.stdout.write(GREEN)
            sub_type="module"
        elif type(sub) is types.InstanceType and sub.__class__ is intromit_container:
            sys.stdout.write(CYAN)
            sub_type="context"
        print format % (a), sub_type

def mkdir_ex(ctx_name):
    if not ctx_name[0].isalpha() and not ctx_name[0] is "_":
        print "a variable-like name is required for context name"
        return
    exec(get_ctx_str()+"."+ctx_name+"=intromit_container(get_ctx_str(), '"+ctx_name+"')")

def cd_ex(ctx_name):
    if ctx_name.startswith(".."):
        if get_ctx() is __main__:
            print "already at root"
        else:
            set_ctx(get_ctx()._get_parent())
            ctx_name=ctx_name[2:]
            if len(ctx_name) and ctx_name[0] is "/": ctx_name=ctx_name[1:]
            if ctx_name: cd_ex(ctx_name)
    else:
        ctx_next=None
        if ctx_name.find(".")>=0:
            ctx_next=ctx_name[ctx_name.find(".")+1:]
            ctx_name=ctx_name[:ctx_name.find(".")]

        if hasattr(get_ctx(), ctx_name):
            dir=getattr(get_ctx(), ctx_name)
            if type(dir) is types.InstanceType and dir.__class__ is intromit_container:
                set_ctx(get_ctx_str()+"."+ctx_name)
            else:
                print ctx_name+" is not a valid context"
                return
        else:
            print "no such context"
            return

        if ctx_next: cd_ex(ctx_next)

def pwd_ex():
    if get_ctx() is __main__: print "<root>"
    else: print get_ctx()._get_name()

def exec_ex(command):
    exec(command, get_ctx().__dict__)

class intromit_container:
    "intromit_container"

    def __init__(self, parent, name):
        self._parent=parent
        self._name=name

    def _get_parent(self):
        return self._parent

    def _get_name(self):
        if eval(self._parent) is __main__: return self._name
        else: return eval(self._parent)._get_name()+"."+self._name

class intromit_pickler(pickle.Pickler):
    "intromit pickler"

    def persistent_id(self, obj):
        if str(type(obj))=="<type '_tmp_.'>" or str(type(obj))=="<type 'pyintromit.container'>":
            return "unk:"+str(type(obj))
        if type(obj) is types.ModuleType: # do not pickle module objects
            if hasattr(obj, "__file__"):
                return "mod:"+obj.__file__
            else:
                return "mod:<noname>"+str(obj)

        elif type(obj) is types.InstanceType and obj.__class__ is intromit_container:
            # intromit_container -> remove attributes
            if hasattr(obj, "__builtins__"): delattr(obj, "__builtins__")

class intromit_unpickler(pickle.Unpickler):
    "intromit unpickler"

    def persistent_load(self, pid):
        if pid.startswith("mod:"):
            if pid.endswith(".intromit"):
                try:
                    return __main__.__intromit__.load(pid[len("mod:"):-len(".intromit")])
                except:
                    print "cannot load %s module" % pid[len("mod:"):-len(".intromit")]
        elif pid.startswith("unk:"):
            return "<lost object, was: "+pid[4:]+">"
        return None

def intromit_dumps(obj, protocol=None):
    file = StringIO()
    intromit_pickler(file, protocol).dump(obj)
    return file.getvalue()

def intromit_dump(obj, filename, protocol=None):
    try: file=open(filename, "wb")
    except Exception, inst:
        print "cannot pickle: ", inst
        return
    intromit_pickler(file, protocol).dump(obj)

def intromit_loads(str):
    file = StringIO(str)
    return intromit_unpickler(file).load()

def intromit_load(filename):
    try: file=open(filename, "r")
    except Exception, inst:
        print "cannot unpickle: ", inst
        return (None, {})
    return intromit_unpickler(file).load()

def intromit_save_context(filename, protocol=None):
    intromit_dump((sys.modules, __main__.__dict__), filename, protocol)

def intromit_load_context(filename):
    dict=intromit_load(filename)[1]
    for k in dict.keys():
        if dict[k]:
            if hasattr(__main__, k): delattr(__main__, k)
            setattr(__main__, k, dict[k])


if "__intromit__" in dir(__main__):
    for i in [j for j in dir() if not j.startswith("_")]:
        exec("__main__.__intromit__."+i+"="+i)
